﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;

namespace Spotless.Services
{
    public enum FilterTypes : byte
    {
        Contains = 0,
        DoesNotContain = 7,
        EqualTo = 1,
        NotEqualTo = 2,
        GreaterThan = 3,
        GreaterThanOrEqual = 5,
        LessThan = 4,
        LessThanOrEqual = 6
    }

    public interface IFilterExpression
    {
    }

    public class FilterExpression<TModel> : IFilterExpression
    where TModel : class
    {
        public FilterExpression(System.Linq.Expressions.Expression<Func<TModel, string>> expression, FilterTypes filterType)
        {
            SetCurrentExpression(expression);
            this.FilterType = filterType;
        }

        public FilterExpression(System.Linq.Expressions.Expression<Func<TModel, byte>> expression, FilterTypes filterType)
        {
            SetCurrentExpression(expression);
            this.FilterType = filterType;
        }

        public FilterExpression(System.Linq.Expressions.Expression<Func<TModel, byte?>> expression, FilterTypes filterType)
        {
            SetCurrentExpression(expression);
            this.FilterType = filterType;
        }

        public FilterExpression(System.Linq.Expressions.Expression<Func<TModel, DateTime>> expression, FilterTypes filterType)
        {
            SetCurrentExpression(expression);
            this.FilterType = filterType;
        }

        public FilterExpression(System.Linq.Expressions.Expression<Func<TModel, DateTime?>> expression, FilterTypes filterType)
        {
            SetCurrentExpression(expression);
            this.FilterType = filterType;
        }

        public FilterExpression(System.Linq.Expressions.Expression<Func<TModel, int>> expression, FilterTypes filterType)
        {
            SetCurrentExpression(expression);
            this.FilterType = filterType;
        }

        public FilterExpression(System.Linq.Expressions.Expression<Func<TModel, int?>> expression, FilterTypes filterType)
        {
            SetCurrentExpression(expression);
            this.FilterType = filterType;
        }

        public FilterExpression(System.Linq.Expressions.Expression<Func<TModel, bool>> expression, FilterTypes filterType)
        {
            SetCurrentExpression(expression);
            this.FilterType = filterType;
        }

        public FilterExpression(System.Linq.Expressions.Expression<Func<TModel, bool?>> expression, FilterTypes filterType)
        {
            SetCurrentExpression(expression);
            this.FilterType = filterType;
        }

        private Expression CurrentExpression;

        private void SetCurrentExpression(LambdaExpression expression)
        {
            CurrentExpression = expression.Body;
        }

        public FilterTypes FilterType { get; private set; }

        private Type GetValueType(Expression expression)
        {
            if (expression.NodeType == ExpressionType.MemberAccess)
            {
                return ((MemberExpression)expression).Type;
            }
            else if (expression.NodeType == ExpressionType.Call)
            {
                MethodCallExpression methodCall = (MethodCallExpression)expression;
                if (methodCall.Arguments.Count == 1)
                {
                    return methodCall.Arguments[0].Type;
                }
                else
                {
                    return methodCall.Method.ReturnType;
                }
            }
            else
            {
                throw new NotImplementedException(string.Format("NodeType '{0}' not implimented", expression.NodeType));
            }
        }

        private ParameterExpression FindOriginalParemeter(Expression expression)
        {
            var current = expression;
            while (current.NodeType != ExpressionType.Parameter)
            {
                if (current.NodeType == ExpressionType.MemberAccess)
                {
                    current = ((MemberExpression)current).Expression;
                }
                else if (current.NodeType == ExpressionType.Call)
                {
                    var methodCall = (MethodCallExpression)current;
                    if (methodCall.Arguments.Count > 0)
                    {
                        current = ((MethodCallExpression)current).Arguments[0];
                    }
                    else
                    {
                        current = methodCall.Object;
                    }
                }
                else
                {
                    throw new NotImplementedException(string.Format("Node Type '{0}' not implimented", current.NodeType));
                }
            }
            return (ParameterExpression)current;
        }

        private bool IsNullable<T>(Type valueType)
        {
            return valueType.IsGenericType && valueType.GetGenericArguments().First() == typeof(T);
        }

        private bool IsNullable(Type valueType)
        {
            return valueType.IsGenericType;
        }

        public IQueryable<TModel> Filter(IQueryable<TModel> source, string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return source;
            }

            Type valueType = GetValueType(CurrentExpression);
            object val = null;
            if (valueType == typeof(string))
            {
                val = value;
            }
            else if (valueType == typeof(int) || IsNullable<int>(valueType))
            {
                int tmp = -1;
                if (int.TryParse(value, out tmp))
                {
                    val = tmp;
                }
            }
            else if (valueType == typeof(byte) || IsNullable<byte>(valueType))
            {
                byte tmp = byte.MinValue;
                if (byte.TryParse(value, out tmp))
                {
                    val = tmp;
                }
            }
            else if (valueType == typeof(DateTime) || IsNullable<DateTime>(valueType))
            {
                DateTime tmp = DateTime.MinValue;
                if (DateTime.TryParse(value, out tmp))
                {
                    val = tmp;
                }
            }
            else if (valueType == typeof(bool) || IsNullable<bool>(valueType))
            {
                bool tmp = false;
                if (bool.TryParse(value, out tmp))
                {
                    val = tmp;
                }
            }
            else
            {
                throw new NotImplementedException(string.Format("ValueType '{0}' not implimented", valueType.ToString()));
            }


            if (val != null)
            {

                BinaryExpression expression = null;

                Expression argument = null;
                if (IsNullable(GetValueType(CurrentExpression)))
                {
                    argument = Expression.Convert(Expression.Constant(val), GetValueType(CurrentExpression));
                }
                else
                {
                    argument = Expression.Constant(val);
                }

                switch (FilterType)
                {
                    case FilterTypes.EqualTo:
                        expression = Expression.Equal(this.CurrentExpression, argument);
                        break;
                    case FilterTypes.NotEqualTo:
                        expression = Expression.NotEqual(this.CurrentExpression, argument);
                        break;
                    case FilterTypes.GreaterThan:
                        expression = Expression.GreaterThan(this.CurrentExpression, argument);
                        break;
                    case FilterTypes.GreaterThanOrEqual:
                        expression = Expression.GreaterThanOrEqual(this.CurrentExpression, argument);
                        break;
                    case FilterTypes.LessThan:
                        expression = Expression.LessThan(this.CurrentExpression, argument);
                        break;
                    case FilterTypes.LessThanOrEqual:
                        expression = Expression.LessThanOrEqual(this.CurrentExpression, argument);
                        break;
                    case FilterTypes.Contains:
                    case FilterTypes.DoesNotContain:
                        var containsMethod = valueType.GetMethod("Contains", new Type[] { typeof(string) });
                        var methodAccess = Expression.Call(this.CurrentExpression, containsMethod, argument);
                        switch (FilterType)
                        {
                            case FilterTypes.Contains:
                                expression = Expression.Equal(methodAccess, Expression.Constant(true));
                                break;
                            case FilterTypes.DoesNotContain:
                                expression = Expression.Equal(methodAccess, Expression.Constant(false));
                                break;
                        }
                        break;
                }

                Expression<Func<TModel, bool>> predicate = Expression.Lambda<Func<TModel, bool>>(
                    expression, FindOriginalParemeter(this.CurrentExpression));
                return source.Where<TModel>(predicate);
            }
            return source;
        }
    }
}
